/**
 * This file is part of XaMauLo 
 * File :                       SchemaXML.scala
 * Author's email :             laure.conte@gmail.com
 *                              sophie.couillez@gmail.com
 **/

package utils;

import scala.xml.{Node, Comment, NodeSeq, Elem}
import org.xml.sax
import objects._
import scala.collection.mutable.HashMap
import java.util.logging.Logger

/**
 *
 * Produce a class in order to manage the XML Schema (as a tree)
 *
 **/
class SchemaXML(path:String){
  var listError : List[Error]=List();
  val file = new java.io.File(path);
  val p = scala.xml.parsing.ConstructingParser.fromFile(file, true);  /*preserve whitespace*/
  val document: xml.Document = p.document;
  var com : StringBuilder=new StringBuilder();
  
  protected final val logger = Logger.getLogger("SchemaXML") 
  
  /**
   * search the element's structure who is represented by its type.
   * @param noeuds the nodes
   * @param searchType the searched type
   * @return the structure of the node : itself and its children
   **/
  def searchNodeWithTypeIntoSchema(noeuds:Seq[Node], searchType:String):Seq[Node] = {
    //println("searchNodeWithTypeIntoSchema : " + searchType)
    //println("Verification : " + noeuds)
    for(val noeud <- noeuds) yield noeud match {
      case n @ <xs:complexType>{ _* }</xs:complexType> if n \ "@name" == searchType 
             => println("ici trouve!!!");return n
      case n @ <xs:simpleType>{ _* }</xs:simpleType> if n \ "@name" == searchType 
             => return n
      //case n:Seq[Node] if((n \\ "complexType").length > 1 || (n \\ "simpleType").length > 1) 
        //     => return searchNodeWithTypeIntoSchema(n.child, searchType)
      case n @ <xs:schema>{ _* }</xs:schema> => return searchNodeWithTypeIntoSchema(n.child, searchType)
      case n => 
             
    } 
    println("ERROR type " + searchType + " non défini");
    return (p.document.children \ "laure") 
  }
  
  
  def searchEltIntoSchemaWithRef(noeuds:Seq[Node], searchLbl:String):Seq[Node] = {
    for(val noeud <- noeuds) yield noeud match {
    case n @ <xs:schema>{ _* }</xs:schema> => return searchEltIntoSchemaWithRef(n.child, searchLbl)
      case n @ <xs:element>{ _* }</xs:element> if n \ "@name" == searchLbl => return n
      case n => //println(searchLbl + " / " + n \ "@name") 
    }
    //println("Sortie")
    return (p.document.children \ "laure")
  }
  
  /** 
    * search the node who is label equals the parameter
    * @param noeuds the sequence of the matching node
    * @param searchLbl the searching label
    **/
    def searchEltIntoSchema(noeuds:Seq[Node], searchLbl:String):Seq[Node] = { 
     //println("searchEltIntoSchema : " + searchLbl + " \n les noeuds : " + noeuds) 
     for(val noeud <- noeuds) yield noeud match { 
       case n @ <xs:element>{ _* }</xs:element> if n \ "@name" == searchLbl => return n 
       //case n @ <xs:element>{ _* }</xs:element> if (n \ "_").length > 0 => return searchEltIntoSchema(n.child, searchLbl)
       case n @ <xs:element>{ _* }</xs:element> if n \ "@ref" == searchLbl =>  return searchEltIntoSchemaWithRef(document.children, searchLbl) 
       case n => //println("AHAHAAHAHAH" + n)
     } 
     for(val noeud <- noeuds) yield noeud match {  
       case n:Seq[Node] if (n.child.length > 1) => return searchEltIntoSchema(n.child, searchLbl) 
       case n => 
     }
    (noeuds \ "laure")
    } 

  
  /**
     * search the node who is label equals the parameter
     * @param noeuds the sequence of the matching node
     * @param searchLbl the searching label
     **/
    def eltInSchema(noeuds:Seq[Node], searchLbl:String):Boolean = {
      for(val noeud <- noeuds) yield noeud match {
        case n @ <xs:element>{ _* }</xs:element> if n \ "@name" == searchLbl => true
        //case n @ <xs:element>{ _* }</xs:element> => return eltInSchema(n.child, searchLbl)
        case n:Seq[Node] if (n.child.length > 1)=> return eltInSchema(n.child, searchLbl)
      }
      false
    }
  
  /**
   *return is the argument type is a XS type with a regexp
   * @param theType the tested type 
   * @return the result
   **/
  def isDefineType(theType:String):Boolean = {
    val t: Seq[Char] = theType
    t match {
      case Seq('x','s',':', rest @ _*) => false
      case Seq() => false
      case Seq(_*) => true
    }
  }
 
  //Retourne le ou les elements racines trouves dans le schema XML
  /**
   * Return the elements racines find in the XML shema
   * @return the sequence of the node 
   **/
  def getRacines():Seq[Node] = { 
    (document.children \ "element")
  }
  
  /**
   * Find a node into the schema
   * @param parents the list of the String
   * @param parcours the matching node
   * @return the found node
   **/
  def findNode(parents:List[String], parcours:Seq[Node]):Seq[Node] = parents match {
    case dad::enfants => //println("findNode : " + dad) 
         val noeud = searchEltIntoSchema(parcours, dad); //on filtre pour éviter les elements null
         noeud.length match {
           case 0 => listError=new Error("MissingElt",dad)::listError; logger.warning("le noeud " + dad + " est vide"); return noeud;
           case _ => for(n <- noeud.filter(p => p != null)) yield n match {
             case <xs:element>{_*}</xs:element> => isDefineType((n \ "@type").text) match {
               case true => return findNode(enfants, searchNodeWithTypeIntoSchema(document.children, (n \ "@type").text))
               case false => return findNode(enfants, n)
             }
             case _ => listError=new Error("MissingElt",dad)::listError; return null; //ERREUR le noeud parents n'a pas été trouvé mauvais positionnement de l'élément dans le document XML 
           }
         }
    case _ => return parcours
  }
  
  /**
   * V�rifie que les �l�ments sont bien dans l'ordre d�crit dans le sch�ma XML
   **/
  def inOrder():Boolean = {
    true
  }
  
  /**
   * test if all element describe in the schema are present in the docuement
   * @param all the list of the sons
   * @param noeuds the sequence of nodes
   **/
  def allEltArePresent(all:List[String], noeuds:Seq[Node]):Boolean = {
     println("allEltArePresent : ")
    for(val noeud <- noeuds) yield noeud match {
      case n @ <xs:element>{_*}</xs:element> =>  
      		val name = (n \ "@name").text match {
                        case "" => (n \ "@ref").text
                        case _ => (n \ "@name").text
                }
                val nbElmt = all.count(p => p == name);
                      var min = (n\"@minOccurs").text match {  
                        case "" =>  1
                        case _ => (n\"@minOccurs").text.toInt
                        //Verifier si c bien un chiffre
                }
                var max = (n\"@maxOccurs").text match {
                        case "" => 1
                        case "unbounded" =>  999999999
                        case _ => (n\"@maxOccurs").text.toInt
                        //VERIFIER SI C BIEN UN CHIFFRE
                }    
                println("element : " + name + "\n nombre element : "+nbElmt + "\n min : " + min + "\n max : " + max);
                com.append("element : " + name);
                com.append("\n");
                com.append("nombre element : "+nbElmt);
                com.append("\n");
                com.append("min : " + min)
                com.append("\n");
                com.append("max : " + max);
                com.append("\n----------------------------\n");
                (all.contains(name)) match {
                  case false if(min>0) => listError=new Error("MissingSon",name)::listError; logger.warning("ERREUR le fils n'existe pas : " + name); return false
                  case true => (nbElmt <= max) match {
                    case false =>listError=new Error("nbOccursSupp",name)::listError; logger.warning("ERREUR trop d'élement!!!" + name); return false
                    case true => (nbElmt >= min) match {
                      case false => listError=new Error("nbOccursInf",name)::listError;logger.warning("ERREUR pas assez d'élement!!!" + name); return false
                      case true => 
                    }
                  }
                  case false => //Okay l'element etait facultatif
                }
      case n => //println("rien")
    } 
    true
  }
  
  /**
   * Test if all balise are in the schema XML
   * @param sons the list of balise present in the XML document
   * @param noeuds the sequence of the node define in the XML schema
   **/
  def allInSchema(sons:List[String], noeuds:Seq[Node]):Boolean = sons match {
    case child::brothers if(eltInSchema(noeuds, child)) => println("allInSchema");return allInSchema(brothers, noeuds)
    case child::brothers => println("allInSchema");false
    case _ => println("allInSchema");true
  }
  
  /**
   * test if all children are presents or/and in order
   * @param sons list of children 
   * @param noeuds sequence of node
   **/
  def checkChildren(sons:List[String], noeuds:Seq[Node], spec:String):Boolean = {
    for(val noeud <- noeuds) yield noeud match {
      case n @ <xs:sequence>{ _* }</xs:sequence> => (allEltArePresent(sons, n.child) && inOrder()) match {
        case false => logger.warning("Incorrect1 : " + sons.toString + "\n" + n + "\n"); return false
        case true => //Console.println("log1"); logger.info("Correct : " + n + "\n")
      }
      case n @ <xs:choice>{ _* }</xs:choice> => (allInSchema(sons, n.child)) match {
        case false => logger.warning("Incorrect2 : " + sons.toString + "\n" + n + "\n"); return false
        case true => //Console.println("log2"); logger.info("Correct : " + n + "\n")
      }
      case n @ <xs:all>{ _* }</xs:all> => (allEltArePresent(sons, n.child)) match {
        case false => logger.warning("Incorrect3 : " + sons.toString + "\n" + n + "\n"); return false
        case true => //Console.println("log3"); logger.info("Correct : " + n + "\n")
      }
      case n @ <xs:complexType>{ _* }</xs:complexType> => return checkChildren(sons, n.child, "")
      case n @ <xs:simpleType>{ _* }</xs:simpleType> => return checkChildren(sons, n.child, "")
      case n @ <xs:simpleContent>{ _* }</xs:simpleContent> => return checkChildren(sons, n.child, "content")
      case n @ <xs:complexContent>{ _* }</xs:complexContent> => return checkChildren(sons, n.child, "content")
      case n @ <xs:annotation>{ _* }</xs:annotation> if(spec.equals("content")) => //rien a faire commentaire ou checkAnnotation
      case n @ <xs:extension>{ _* }</xs:extension> => spec match {
        case "content" => 
        case _ => listError=new Error("PbSyntaxeXSD","xs:extension")::listError;
      }
      case n @ <xs:restriction>{ _* }</xs:restriction> => spec match {
        case "content" => 
        case _ => listError=new Error("PbSyntaxeXSD","xs:restriction")::listError;
      }
      case n => //xs:element, xs:attribute, xs:attributegroup, ??? 
      //xs:any: permet de définir n'importe quel element dans le document
    }
    true;
  }
  
  /**
   * test if alla attributes are presents 
   * @param atts list of attributes
   * @param noeuds sequence of node
   **/ 
   def checkAttributes(atts:HashMap[String, String], noeuds:Seq[Node]):Boolean = {
     for(val noeud <- noeuds) yield noeud match {
       case n @ <xs:attributegroup>{ _* }</xs:attributegroup> => checkAttributes(atts, n.child)
       case n @ <xs:attribute>{ _* }</xs:attribute> => atts.contains((n \ "@name").text) match {
         case true if "prohibited".equals((n \ "@use").text) => logger.warning("ERREUR on a un attribut en trop!!!"); false
         case true => atts.removeKey((n \ "@name").text)
         case false if "required".equals((n \ "@use").text) => logger.warning("ERREUR il manque un attribut"); false
         case false => 
       }
       case n if (n \\ "attribute").length > 0 => checkAttributes(atts, n.child)
       case n => 
     }
     return true
   }
}


